package org.sundatagroup.aurora.show.service;

import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.WebSocketSession;
import org.sundatagroup.aurora.core.service.message.MessageCenter;
import org.sundatagroup.aurora.show.controller.DisplaySocketController;
import org.sundatagroup.aurora.show.entity.status.DisplayStatus;
import org.sundatagroup.aurora.show.entity.status.IStatusEntity;
import org.sundatagroup.aurora.show.entity.status.PlayingStatus;
import org.sundatagroup.aurora.show.entity.status.ReadyStatus;
import org.sundatagroup.aurora.show.entity.status.StatusAndSession;
import org.sundatagroup.aurora.show.entity.status.WaitingStatus;

import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;

/**
 * <p>屏幕状态服务
 * <p>原类名: StatusQuerier
 * </p>
 * 
 * @version 0.2.0
 * <br>补充消息传递区域, 允许向展示屏传递数据
 * 
 * @author lui
 * @author Zdream
 * @date 2018-03-30
 * @since 0.1
 */
@Service
public class ScreenStatusService {
	
	@Autowired
	SidDispatcher sidDispatcher;

	@Autowired
	private MessageCenter center;
	
	/*
	 * 注意, 这个在 Controller 层
	 */
	@Autowired
	private DisplaySocketController socketCtrl;
	
	private final static Logger LOGGER = LoggerFactory.getLogger(ScreenStatusService.class);
	
	/**
	 * sid - 状态数据映射
	 */
	private HashMap<String, StatusAndSession> statusMap = new HashMap<>();
	
	/**
	 * WebSocketSession id - sid 映射
	 */
	private HashMap<String, String> sessionMap = new HashMap<>();
	
	/**
	 * 查询 session 对应的屏幕 id
	 * @param session
	 * @return
	 */
	public String searchSidByWebSocket(WebSocketSession session) {
		return sessionMap.get(session.getId());
	}
	
	/* **********
	 * 状态变更 *
	 ********** */
	/*
	 * 该方法直接处理从 endPoint 发送过来的状态变更申请
	 */
	
	/**
	 * <p>建立连接, 这里需要分情况处理.
	 * <li>如果该 sid 存在, 说明这个展示屏刚刚是播放完其它工程,
	 * 或者是从等待页跳转过来, 那么就不应该重新重置该展示屏的状态;
	 * <li>如果该 sid 不存在, 那么新建这个展示屏及状态数据, 并且设置状态为“等待”
	 * </li>
	 * </p>
	 */
	public void open(String sid, WebSocketSession session) {
		if (statusMap.containsKey(sid)) {
			StatusAndSession status = statusMap.get(sid);
			status.setConnect(true);
			status.setSocketSession(session);
			
			sessionMap.put(session.getId(), sid);
		} else {
			WaitingStatus wait = new WaitingStatus(sid);
			StatusAndSession status = new StatusAndSession(wait, session, true);
			
			statusMap.put(sid, status);
			sessionMap.put(session.getId(), sid);

			center.publish("show-screen-status-create", new JsonPrimitive(sid));
		}
	}
	
	/**
	 * 关闭连接, 但不是删除状态
	 */
	public void close(String sid, WebSocketSession session) {
		StatusAndSession status = statusMap.get(sid);
		
		status.setConnect(false);
		status.setSocketSession(null);
		status.setTimestamp(Instant.now());
		
		sessionMap.remove(session.getId());

		center.publish("show-screen-status-update", new JsonPrimitive(sid));
	}
	
	/**
	 * 状态修改
	 */
	public void updateStatus(String sid, IStatusEntity status) {
		if (status == null) {
			throw new NullPointerException("status 不允许为空");
		}
		
		StatusAndSession s = statusMap.get(sid);
		
		// 特殊照顾
		if (status.getStatus() == DisplayStatus.PLAYING) {
			PlayingStatus p = (PlayingStatus) status;
			p.setPid(s.getPid());
		} else if (status.getStatus() == DisplayStatus.READY) {
			ReadyStatus p = (ReadyStatus) status;
			s.setPid(p.getPid());
		}
		status.setSid(sid);
		
		// 其它
		s.setStatus(status);

		center.publish("show-screen-status-update", new JsonPrimitive(sid));
	}
	
	/**
	 * 指定屏幕播放指定 project
	 * @param sid
	 * @param pid
	 */
	public void attachPid(String sid, int pid) {
		StatusAndSession s = statusMap.get(sid);
		s.setPid(pid);
	}
	
	/* **********
	 * 状态查询 *
	 ********** */
	
	/**
	 * 屏幕显示详细信息查询.<br>
	 * @param sid
	 *   screen ID 屏幕号
	 * @return
	 *   返回 {@link IStatusEntity}
	 */
	public IStatusEntity query(String sid) {
		if (statusMap.containsKey(sid)) {
			return statusMap.get(sid).getStatus();
		}
		return null;
	}

	/**
	 * 所有屏幕显示详细信息查询.<br>
	 * 
	 * @return 返回 List&lt;IStatusEntity&gt;
	 * @since 0.1.1
	 */
	public List<IStatusEntity> queryAll() {
		Iterator<Entry<String, StatusAndSession>> it = statusMap.entrySet().iterator();
		List<IStatusEntity> allStatus = new ArrayList<>();
		while (it.hasNext()) {
			Map.Entry<String, StatusAndSession> entry = it.next();
			allStatus.add(entry.getValue().getStatus());
		}
		LOGGER.info("现在后台登记的状态总数: " + allStatus.size());
		return allStatus;
	}
	
	/**
	 * 查询指定的屏幕是否在连接状态
	 * @param sid
	 * @return
	 * @since 0.2.0
	 */
	public boolean isConnect(String sid) {
		return statusMap.get(sid).isConnect();
	}
	
	/**
	 * 返回还在追踪的展示屏的数量
	 * @return
	 */
	public int count() {
		return statusMap.size();
	}
	
	/**
	 * 过滤出指定时间窗口内, 没有状态变化的、没有在连接状态的展示屏
	 * @param timeWindow
	 *   运行时间窗口, 允许的时间差, 以毫秒为单位
	 * @param currentTime
	 *   现在计算中的时间点
	 * @return
	 *   返回满足情况的屏幕 id 的列表
	 * @since 0.1.1
	 */
	public List<String> timerFilt(int timeWindow, Instant currentTime) {
		Iterator<Entry<String,StatusAndSession>> it = statusMap.entrySet().iterator();
		List<String> result = new ArrayList<>();
		while (it.hasNext()){
			Map.Entry<String, StatusAndSession> entry = it.next();
			StatusAndSession s = entry.getValue();
			
			if (s.isConnect()) {
				continue;
			}
			
			Duration d = Duration.between(s.getTimestamp(), currentTime);
			if (d.toMillis() > timeWindow) {
				result.add(entry.getKey());
			}
		}
		return result;
	}

	/**
	 * 解除展示屏追踪
	 * 从状态列表中强制删除该展示屏及相关数据, 并关闭其连接
	 * @param sid
	 */
	public void destroy(String sid){
		if (statusMap.containsKey(sid)) {
			StatusAndSession s = statusMap.get(sid);
			
			// 下面情况基本不可能遇到, 但是防止万一, 依然补充该流程
			if (s.getSocketSession() != null && s.getSocketSession().isOpen()) {
				try {
					s.getSocketSession().close();
				} catch (IOException e) {}
				
				sessionMap.remove(s.getSocketSession().getId());
			}
			statusMap.remove(sid);
			LOGGER.info("解除展示屏追踪: " + sid);

			center.publish("show-screen-status-close", new JsonPrimitive(sid));
		}
	}
	
	/* **********
	 * 消息传递 *
	 ********** */
	/*
	 * @since 0.2.0
	 * 补充向前端展示屏发送消息的方法
	 */
	
	/**
	 * 向指定的展示屏发送消息数据
	 * @param sid
	 *   展示屏 id
	 * @param data
	 *   待发送的数据
	 */
	public void pushMessage(String sid, JsonObject data) {
		socketCtrl.pushCommand(statusMap.get(sid).getSocketSession(), data);
	}

}
